55. 绘制直方图

本章概要

  • 学习材料:中性收益率与订单金额教学示例。
  • 本章任务:比较箱数、计数/密度口径并绘制Q-Q图。
  • 完成后你将得到:直方图、箱数敏感性、理论分位数和排序样本。
  • 自我检查:检查箱计数和等于样本数且Q-Q两轴单调;不得写成真实订单分布结论。
  • 拓展练习:把Q-Q方法应用到另一中性金额序列。

本章导入

Listing 1: 导入本章所需的基础库
展开代码
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
plt.rcParams['font.sans-serif'] = ['Source Han Serif SC']  # 使用课程规定且已安装的思源宋体显示中文
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

直方图是什么?

直方图(Histogram)是展示数据分布最直观的工具。

在金融分析中,直方图帮助我们:

  • 理解分布形态:数据是对称、偏斜还是多峰
  • 识别异常值:远离主分布的孤立值
  • 评估风险:分布的尾部形态决定极端事件概率
  • 比较群体:不同资产、不同时期的分布差异

直方图的数学原理——分箱与计数

直方图是概率密度函数(PDF)的离散近似,分为三步:

  1. 分箱(Binning):将数据范围分成 \(k\) 个区间(bin)
  2. 计数(Counting):统计每个区间内的数据点数
  3. 归一化(Normalization):转换为概率或密度

直方图的数学定义

设数据集 \(X = \{x_1, x_2, \ldots, x_n\}\),分箱边界为 \(b_0, b_1, \ldots, b_k\)

\[ \large \text{count}_i = |\{x_j | b_{i-1} \leq x_j < b_i\}| \]

频率(Frequency):

\[ \large f_i = \frac{\text{count}_i}{n} \]

直方图的密度定义

密度(Density):

\[ \large d_i = \frac{f_i}{b_i - b_{i-1}} = \frac{\text{count}_i}{n \cdot (b_i - b_{i-1})} \]

运行前预测|平台任务1解答代码

  • 输入预测:运行前先写出 stock_pricestock_return 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到stock_return 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务1解答代码”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务1解答代码

展开完整代码(投影默认折叠)
# 注:平台任务代码,依赖平台指定的外部数据URL
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库

# 从Excel文件读取数据存入stock_price
stock_price = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220824/xlsx/1562273067622227968.xlsx",sheet_name="Sheet1",header=0,index_col=0)

stock_price = stock_price.dropna()   #删除缺失值所在的行

stock_return = np.log(stock_price/stock_price.shift(1))  #计算股票收益率

stock_return = stock_return.dropna()     #删除缺失值所在的行

stock_return.describe()  # 查看stock_return的描述性统计量

stock_return = stock_return.dropna()         #删除缺失值所在的行

print(stock_return)  # 输出股票数据

任务复盘|平台任务1解答代码

运行后核对:核对 stock_pricestock_return 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:把数据替换为一个中国企业或市场序列,改变一个分组或时间窗口;说明图形结论是否改变,以及为何。

运行前预测|平台任务2解答代码

  • 输入预测:运行前先写出 stock_pricestock_return 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到stock_return 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务2解答代码”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务2解答代码

展开完整代码(投影默认折叠)
# 注:平台任务代码,依赖平台指定的外部数据URL
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置Matplotlib全局参数
plt.rcParams['axes.unicode_minus']=False  # 设置Matplotlib全局参数

# 从Excel文件读取数据存入stock_price
stock_price = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220824/xlsx/1562273067622227968.xlsx",sheet_name="Sheet1",header=0,index_col=0)
stock_price = stock_price.dropna()   #删除缺失值所在的行

stock_return = np.log(stock_price/stock_price.shift(1))  #计算股票收益率
stock_return = stock_return.dropna()     #删除缺失值所在的行
plt.figure(figsize=(9,9))  # 创建图形画布
plt.subplot(2,1,1)  # 选择子图位置
plt.plot(stock_return["东方航空(A股)"],"r-",label=u"东方航空(A股)",lw=2)  # 绘制折线图
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylim(-0.12,0.15)  # 设置Y轴显示范围
plt.ylabel(u"收益率",fontsize=13,rotation=90)  # 设置Y轴标签
plt.legend(loc=9,fontsize=13)     #图例放在中上位置
plt.grid()  #加入网格线
plt.subplot(2,1,2) #代表第二行的子图
plt.plot(stock_return["东方航空(美股)"],"b-",label=u"东方航空(美股)",lw=2)  # 绘制折线图
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.xlabel(u"日期",fontsize=13)  # 设置X轴标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylim(-0.12,0.15)  # 设置Y轴显示范围
plt.ylabel(u"收益率",fontsize=13,rotation=90)  # 设置Y轴标签
plt.legend(loc=9,fontsize=13)     #图例放在中上位置
plt.grid()  #加入网格线
plt.show()  # 显示图形
plt.savefig("1.png")  # 保存图形至文件

任务复盘|平台任务2解答代码

运行后核对:核对 stock_pricestock_return 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:把数据替换为一个中国企业或市场序列,改变一个分组或时间窗口;说明图形结论是否改变,以及为何。

运行前预测|平台任务3解答代码

  • 输入预测:运行前先写出 stock_pricestock_returnSHI_return 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到SHI_return 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务3解答代码”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务3解答代码

展开完整代码(投影默认折叠)
# 注:平台任务代码,依赖平台指定的外部数据URL及tkinter模块
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
from tkinter import font  # 导入font模块
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置Matplotlib全局参数
plt.rcParams['axes.unicode_minus']=False  # 设置Matplotlib全局参数

# 从Excel文件读取数据存入stock_price
stock_price = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220824/xlsx/1562273067622227968.xlsx",sheet_name="Sheet1",header=0,index_col=0)
stock_price = stock_price.dropna()   #删除缺失值所在的行

stock_return = np.log(stock_price/stock_price.shift(1))  #计算股票收益率
stock_return = stock_return.dropna()     #删除缺失值所在的行
SHI_return = np.array(stock_return.iloc[:,2:]) #将上海石化A股和美股日收益率转为数组形式
plt.figure(figsize=(9,10))  # 创建图形画布
plt.subplot(2,1,1)  # 选择子图位置
plt.hist(SHI_return,label=[u"上海石化A股日收益率",u"上海石化美股日收益率"],stacked=True,edgecolor="k",bins=30)  #以堆叠形式展出
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylabel(u"频数",fontsize=13,rotation=90)  # 设置Y轴标签
plt.legend(fontsize=13)  # 添加图例
plt.grid()  # 显示网格线
plt.subplot(2,1,2)  # 选择子图位置
plt.hist(SHI_return,label=[u"上海石化A股日收益率",u"上海石化美股日收益率"],edgecolor="k",bins=30) #以并排形式展出
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylabel(u"频数",fontsize=13,rotation=90)  # 设置Y轴标签
plt.xlabel(u"股票日收益率",fontsize=13)  # 设置X轴标签
plt.legend(fontsize=13)  # 添加图例
plt.grid()  # 显示网格线
plt.savefig("2.png")  # 保存图形至文件

任务复盘|平台任务3解答代码

运行后核对:核对 stock_pricestock_returnSHI_return 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:把数据替换为一个中国企业或市场序列,改变一个分组或时间窗口;说明图形结论是否改变,以及为何。

运行前预测|平台任务4解答代码

  • 输入预测:运行前先写出 stock_pricestock_returnCEA_return 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到CEA_return 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务4解答代码”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务4解答代码

展开完整代码(投影默认折叠)
# 注:平台任务代码,依赖平台指定的外部数据URL
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置Matplotlib全局参数
plt.rcParams['axes.unicode_minus']=False  # 设置Matplotlib全局参数

# 从Excel文件读取数据存入stock_price
stock_price = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220824/xlsx/1562273067622227968.xlsx",sheet_name="Sheet1",header=0,index_col=0)
stock_price = stock_price.dropna()   #删除缺失值所在的行

stock_return = np.log(stock_price/stock_price.shift(1))  #计算股票收益率
stock_return = stock_return.dropna()     #删除缺失值所在的行
CEA_return = np.array(stock_return.iloc[:,0:2]) #将东方航空A股和美股收益率转为数组形式
plt.figure(figsize=(9,10))  # 创建图形画布
plt.subplot(2,1,1)  # 选择子图位置
plt.hist(CEA_return,label=[u"东方航空A股日收益率",u"东方航空美股日收益率"],stacked=True,edgecolor="k",bins=30)  #以堆叠形式展出
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylabel(u"频数",fontsize=13,rotation=90)  # 设置Y轴标签
plt.legend(fontsize=13)  # 添加图例
plt.grid()  # 显示网格线
plt.subplot(2,1,2)  # 选择子图位置
plt.hist(CEA_return,label=[u"东方航空A股日收益率",u"东方航空美股日收益率"],edgecolor="k",bins=30) #以并排形式展出
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylabel(u"频数",fontsize=13,rotation=90)  # 设置Y轴标签
plt.xlabel(u"股票日收益率",fontsize=13)  # 设置X轴标签
plt.legend(fontsize=13)  # 添加图例
plt.grid()  # 显示网格线
plt.savefig("3.png")  # 保存图形至文件

任务复盘|平台任务4解答代码

运行后核对:核对 stock_pricestock_returnCEA_return 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:把数据替换为一个中国企业或市场序列,改变一个分组或时间窗口;说明图形结论是否改变,以及为何。

关键参数解析

绘制直方图时需关注以下参数:

  • bins(箱数):太少信息损失,太多噪声干扰
  • edgecolor:箱边颜色,'white' 使分箱更清晰
  • alpha:透明度,0.7 使图表不显沉重

bins 的经验法则

  • \(k = \sqrt{n}\)(平方根法则)
  • \(k = \log_2(n)\)(Sturges 法则)

概率密度与直方图叠加

展开代码
# 生成正态分布数据
np.random.seed(42)  # 固定随机种子保证每次课堂运行结果一致
mu, sigma = 0.001, 0.02  # mu:均值,sigma:标准差
data = np.random.normal(mu, sigma, 1000)

# 绘制直方图(密度)
plt.figure(figsize=(8.6, 4.0))
# density=True:将Y轴归一化为概率密度(总面积为1)
n, bins, patches = plt.hist(data, bins=30, density=True,
                            color='#2E86AB', alpha=0.7,
                            edgecolor='white', label='样本分布')

# 叠加理论正态分布曲线
x = np.linspace(data.min(), data.max(), 100)
pdf = (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mu) / sigma)**2)
plt.plot(x, pdf, color='#E3120B', linewidth=2.5, label='理论正态分布')

plt.title('收益率分布与正态拟合', fontsize=16, fontweight='bold')
plt.xlabel('收益率', fontsize=12)
plt.ylabel('概率密度', fontsize=12)
plt.legend(fontsize=11)
plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
Listing 2: 概率密度曲线与直方图叠加
图中展示概率密度曲线与直方图叠加;读者应依据坐标、图例与注释比较主要模式。

正态分布公式与拟合检验

正态分布的数学形式

\[ \large f(x) = \frac{1}{\sigma\sqrt{2\pi}} \exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right) \]

其中:\(\mu\) 为均值(位置参数),\(\sigma\) 为标准差(尺度参数)

拟合检验——KS 检验

  • 比较经验分布与理论分布
  • p 值 > 0.05:无法拒绝正态假设
  • p 值 ≤ 0.05:拒绝正态假设

KS 检验代码演示

Listing 3: Kolmogorov-Smirnov 检验
展开代码
np.random.seed(42)  # 固定随机种子保证每次课堂运行结果一致
from scipy import stats

mu, sigma = 0.001, 0.02
data = np.random.normal(mu, sigma, 1000)

# KS 检验:比较经验分布与理论正态分布
ks_stat, ks_pvalue = stats.kstest(data, 'norm', args=(mu, sigma))
print(f'Kolmogorov-Smirnov检验:')
print(f'统计量: {ks_stat:.4f}')
print(f'p值: {ks_pvalue:.4f}')
print(f'结论: {"无法拒绝正态假设" if ks_pvalue > 0.05 else "拒绝正态假设"}')
Kolmogorov-Smirnov检验:
统计量: 0.0173
p值: 0.9197
结论: 无法拒绝正态假设

分组直方图——不同股票收益率对比

数据口径:模拟数据(名称为标签),非真实行情。

展开代码
# 生成三只股票的收益率数据
np.random.seed(42)  # 固定随机种子保证每次课堂运行结果一致
stocks = {
    '贵州茅台': np.random.normal(0.001, 0.018, 500),   # 低波动
    '中国平安': np.random.normal(0.0008, 0.025, 500),   # 中波动
    '中小板指': np.random.normal(0.0015, 0.035, 500)    # 高波动
}

fig, axes = plt.subplots(1, 3, figsize=(10, 3.2))
colors = ['#E3120B', '#008080', '#2C3E50']

for ax, (stock, data), color in zip(axes, stocks.items(), colors):
    ax.hist(data, bins=25, color=color, alpha=0.7, edgecolor='white')
    ax.axvline(data.mean(), color='black', linestyle='--', linewidth=2)
    ax.set_title(f'{stock}\nμ={data.mean():.4f}, σ={data.std():.4f}',
                 fontsize=12, fontweight='bold')
    ax.set_xlabel('收益率', fontsize=10)
    ax.set_ylabel('频数', fontsize=10)
    ax.grid(axis='y', alpha=0.3)

plt.tight_layout()
plt.show()
图中展示三只模拟股票收益率分布对比;读者应依据坐标、图例与注释比较主要模式。
Figure 1: 三只模拟股票收益率分布对比

年化波动率计算

数据口径:模拟数据(名称为标签),非真实行情。

Listing 4: 各股票模拟年化波动率比较
展开代码
np.random.seed(42)  # 固定随机种子保证每次课堂运行结果一致
stocks = {
    '贵州茅台': np.random.normal(0.001, 0.018, 500),
    '中国平安': np.random.normal(0.0008, 0.025, 500),
    '中小板指': np.random.normal(0.0015, 0.035, 500)
}

print('各股票波动率比较:')
for stock, data in stocks.items():
    # 年化波动率 = 日波动率 × sqrt(252)
    annual_volatility = data.std() * np.sqrt(252)
    print(f'{stock}: 年化波动率 = {annual_volatility:.2%}')
各股票波动率比较:
贵州茅台: 年化波动率 = 28.01%
中国平安: 年化波动率 = 38.77%
中小板指: 年化波动率 = 56.07%

可视化策略对比

绘制分组直方图有三种常见方案:

方案 优点 缺点
子图对比 保持每个分布的完整性 难以直接比较
叠加直方图 直观对比分布差异 颜色叠加可能混淆
核密度估计(KDE) 平滑的分布曲线 细节可能被平滑掉

累积直方图——收益率分位数

展开代码
np.random.seed(42)  # 固定随机种子保证每次课堂运行结果一致
returns = np.random.normal(0.001, 0.02, 1000)

plt.figure(figsize=(8.6, 4.0))
# cumulative=True:绘制累积分布
# density=True:归一化为累积概率
plt.hist(returns, bins=30, cumulative=True, density=True,
         color='#008080', alpha=0.7, edgecolor='white',
         label='累积分布')

# 标注关键分位数
percentiles = [5, 25, 50, 75, 95]
for p in percentiles:
    value = np.percentile(returns, p)
    plt.axvline(value, color='#E3120B', linestyle=':', alpha=0.6)
    plt.text(value, 0.5, f'P{p}', fontsize=9, rotation=90)

plt.title('收益率累积分布', fontsize=16, fontweight='bold')
plt.xlabel('收益率', fontsize=12)
plt.ylabel('累积概率', fontsize=12)
plt.legend(fontsize=11)
plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
Listing 5: 累积直方图——收益率分位数
图中展示累积直方图——收益率分位数;读者应依据坐标、图例与注释比较主要模式。

CDF 分位数:从收益左尾到正值损失 VaR

累积分布函数(CDF):

\[ \large F(x) = P(X \leq x) = \int_{-\infty}^x f(t) dt \]

  • 收益左尾\(Q_{0.05}(r)=F_r^{-1}(0.05)\) 是收益的 5% 分位数,通常为负值
  • 正值损失:定义 \(L=-r\) 后,\(VaR_{95}(L)=Q_{0.95}(L)=-Q_{0.05}(r)\)
  • 报告时需要说明:使用同一组收益样本和一致的经验分位数;说明收益或损失的符号、95% 置信水平、一期持有期和经验估计法

分位数分析

Listing 6: 收益率分位数分析
展开代码
np.random.seed(42)  # 固定随机种子保证每次课堂运行结果一致
returns = np.random.normal(0.001, 0.02, 1000)

return_q05 = np.quantile(returns, 0.05)  # 计算同一示例数据的收益5%左尾分位数
loss_var95 = np.quantile(-returns, 0.95)  # 把收益取负后计算正值损失VaR95
assert np.isclose(loss_var95, -return_q05)  # 核对同一样本下两套符号口径对应同一经济量
print(f'收益Q05={return_q05:.4f}|一期经验损失VaR95={loss_var95:.4f}')  # 同时报出符号、持有期与经验估计法

print('分位数分析:')
for p in [5, 25, 50, 75, 95]:
    value = np.percentile(returns, p)
    print(f'P{p}: {value:.4f}')
收益Q05=-0.0295|一期经验损失VaR95=0.0295
分位数分析:
P5: -0.0295
P25: -0.0120
P50: 0.0015
P75: 0.0140
P95: 0.0345
  • P5:收益的 5% 左尾分位数;转为损失口径后,\(VaR_{95}(L)=-P5\)
  • P50:中位数
  • P95:有 95% 的概率低于此值

二维直方图——两资产联合分布

展开代码
# 生成相关的二维数据
np.random.seed(42)  # 固定随机种子保证每次课堂运行结果一致
mean = [0.001, 0.0008]
cov = [[0.0004, 0.0002], [0.0002, 0.0006]]  # 协方差矩阵
data_2d = np.random.multivariate_normal(mean, cov, 1000)

x = data_2d[:, 0]  # 股票A收益率
y = data_2d[:, 1]  # 股票B收益率

plt.figure(figsize=(7.2, 3.5))
plt.hist2d(x, y, bins=30, cmap='Blues')
plt.colorbar(label='频数')

# 添加均值线
plt.axvline(x.mean(), color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.axhline(y.mean(), color='red', linestyle='--', linewidth=2, alpha=0.7)

plt.title('两资产收益率联合分布', fontsize=16, fontweight='bold')
plt.xlabel('股票A收益率', fontsize=12)
plt.ylabel('股票B收益率', fontsize=12)
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()

correlation = np.corrcoef(x, y)[0, 1]
print(f'相关系数: {correlation:.4f}')
Listing 7: 二维直方图——两资产收益率联合分布
图中展示二维直方图——两资产收益率联合分布;读者应依据坐标、图例与注释比较主要模式。
相关系数: 0.3674

二维分布的数学含义

联合概率密度\(f_{X,Y}(x, y)\)

相关系数

\[ \large \rho_{XY} = \frac{\text{Cov}(X, Y)}{\sigma_X \sigma_Y} \]

金融应用

  • 分散化效果:相关系数越接近 0,分散化效果越好
  • 对冲策略:负相关的资产可以相互对冲
  • 因子暴露:多资产对共同因子的敏感度

核密度估计(KDE)——平滑分布曲线

展开代码
np.random.seed(42)  # 固定随机种子保证每次课堂运行结果一致
from scipy.stats import gaussian_kde

# 生成对数正态分布数据(右偏分布)
data_skewed = np.random.lognormal(0, 0.5, 1000)

fig, axes = plt.subplots(1, 2, figsize=(8.6, 3.5))

# 左:直方图
axes[0].hist(data_skewed, bins=30, density=True,
             color='#2E86AB', alpha=0.7, edgecolor='white')
axes[0].set_title('直方图(离散)', fontsize=14, fontweight='bold')
axes[0].set_xlabel('数值', fontsize=12)
axes[0].set_ylabel('密度', fontsize=12)
axes[0].grid(axis='y', alpha=0.3)

# 右:KDE
kde = gaussian_kde(data_skewed)
x_range = np.linspace(data_skewed.min(), data_skewed.max(), 500)
axes[1].plot(x_range, kde(x_range), color='#E3120B', linewidth=2.5)
axes[1].fill_between(x_range, kde(x_range), alpha=0.3, color='#E3120B')
axes[1].set_title('核密度估计(连续)', fontsize=14, fontweight='bold')
axes[1].set_xlabel('数值', fontsize=12)
axes[1].set_ylabel('密度', fontsize=12)
axes[1].grid(axis='y', alpha=0.3)

plt.tight_layout()
plt.show()

skewness_val = pd.Series(data_skewed).skew()
print(f'偏度: {skewness_val:.4f}')
print(f'结论: {"右偏分布" if skewness_val > 0 else "左偏分布" if skewness_val < 0 else "对称分布"}')
Listing 8: 核密度估计——平滑分布曲线
图中展示核密度估计——平滑分布曲线;读者应依据坐标、图例与注释比较主要模式。
偏度: 2.1377
结论: 右偏分布

KDE 的数学原理

核密度估计公式\(K\) 为核函数(常用高斯核),\(h\) 为带宽,\(n\) 为样本量

\[ \large \hat{f}_h(x) = \frac{1}{nh}\sum_{i=1}^n K\left(\frac{x-x_i}{h}\right) \]

带宽选择\(h\) 太小→过度拟合(跟噪声);\(h\) 太大→过度平滑(丢细节) - Silverman 法则\(h = 1.06\sigma n^{-1/5}\)

固定种子尾部示例数据:正态一致性诊断

数据口径:固定种子 normal + t(3) 教学示例,非真实行情、非估计模型。收益 \(r\) 的损失定义为 \(L=-r\);本章报告 \(VaR_{95}(L)=Q_{0.95}(L)\),正值表示该损失分位数。

展开代码
from scipy import stats

np.random.seed(42)
n = 1000

# 生成预先指定的正态加t分布尾部示例数据
normal_sample = np.random.normal(0, 0.02, n)
financial_returns = normal_sample + np.random.standard_t(3, n) * 0.01

fig, axes = plt.subplots(1, 2, figsize=(9.0, 3.55))

# 左:直方图 vs 正态分布
axes[0].hist(financial_returns, bins=40, density=True,
             color='#2E86AB', alpha=0.7, edgecolor='white', label='模拟分布')
x = np.linspace(financial_returns.min(), financial_returns.max(), 100)
fixture_mean, fixture_std = financial_returns.mean(), financial_returns.std(ddof=1)  # 用同一示例数据估计正态位置与尺度
theoretical_norm = stats.norm.pdf(x, fixture_mean, fixture_std)  # 绘制拟合正态参照而非真实模型
axes[0].plot(x, theoretical_norm, 'r-', linewidth=2, label='理论正态')
axes[0].set_title('固定种子示例数据 vs 拟合正态', fontsize=14, fontweight='bold')
axes[0].set_xlabel('收益率', fontsize=12)
axes[0].set_ylabel('密度', fontsize=12)
axes[0].legend()
axes[0].grid(axis='y', alpha=0.3)

# 右:Q-Q图
stats.probplot(financial_returns, dist='norm', plot=axes[1])
axes[1].set_title('固定种子示例数据 Q-Q 诊断', fontsize=14, fontweight='bold')
axes[1].grid(alpha=0.3)

plt.tight_layout()
plt.show()
Listing 9: 模拟收益率正态性检验
图中展示模拟收益率正态性检验;读者应依据坐标、图例与注释比较主要模式。

固定种子示例数据结果:经验与正态 VaR

Listing 10: 正态性检验统计量
展开代码
from scipy import stats

np.random.seed(42)
n = 1000
normal_sample = np.random.normal(0, 0.02, n)
financial_returns = normal_sample + np.random.standard_t(3, n) * 0.01
losses = -financial_returns  # 按损失等于负收益的口径转换
empirical_var95 = np.quantile(losses, 0.95)  # 计算示例数据损失的经验95%分位数
fitted_mean = financial_returns.mean()  # 估计示例数据收益均值供正态参照
fitted_std = financial_returns.std(ddof=1)  # 估计示例数据收益样本标准差供正态参照
normal_var95 = stats.norm.ppf(0.95, loc=-fitted_mean, scale=fitted_std)  # 计算拟合正态损失95%分位数

print(f'偏度: {pd.Series(financial_returns).skew():.4f}')
print(f'峰度: {pd.Series(financial_returns).kurtosis():.4f}')

# Shapiro-Wilk 检验
shapiro_stat, shapiro_p = stats.shapiro(financial_returns[:5000])
print(f'\nShapiro-Wilk检验: 统计量={shapiro_stat:.4f}, p值={shapiro_p:.4f}')

# Jarque-Bera 检验
jb_stat, jb_p = stats.jarque_bera(financial_returns)
print(f'Jarque-Bera检验: 统计量={jb_stat:.4f}, p值={jb_p:.4f}')
print(f'结论: {"拒绝正态假设" if jb_p < 0.05 else "无法拒绝正态假设"}')
print(f'固定示例数据经验VaR95={empirical_var95:.4f}|拟合正态VaR95={normal_var95:.4f}')  # 同时报告两种同符号损失分位数
偏度: -0.0386
峰度: 0.6775

Shapiro-Wilk检验: 统计量=0.9957, p值=0.0063
Jarque-Bera检验: 统计量=18.8494, p值=0.0001
结论: 拒绝正态假设
固定示例数据经验VaR95=0.0382|拟合正态VaR95=0.0411

有条件的结论:只对这一次固定种子示例数据比较两种分位数;若经验 VaR 高于拟合正态 VaR,只能说“本示例数据下正态参照较低”。更换种子、样本量或分布后必须重算,不能无条件宣称正态法总会低估 VaR。

Q-Q 图解读指南

若总体与拟合正态近似一致,有限样本 Q-Q 点通常在参考线附近随机波动。系统弯曲或尾端偏离提示进一步检查位置、尺度、偏度或尾部,但视觉图形不单独证明分布类型。

金融意义

  • 尾部核对:比较经验分位数、拟合分位数与置信区间
  • VaR 口径:先声明收益/损失符号、置信水平、持有期和估计法
  • 模型边界:仅当样本外依据显示正态参照持续偏低时,才讨论替代尾部模型

本章小结

直方图类型 核心用途 关键参数
基础直方图 查看单变量分布 bins, edgecolor
密度直方图 与理论分布对比 density=True
分组直方图 对比多个分布 子图 / 叠加
累积直方图 分位数分析 cumulative=True
二维直方图 联合分布 plt.hist2d()
核密度估计 平滑分布曲线 gaussian_kde

随堂练习

  • 问题 1|怎样完成本章分析?:用两种箱数绘制同一收益率序列,写出样本数、箱计数总和与尾部敏感性;同时报告 \(Q_{0.05}(r)\)\(VaR_{95}(L)=-Q_{0.05}(r)\),注明收益/损失符号、95% 置信水平、一期持有期和经验分位数估计法。
  • 问题 2|结果说明什么?:说明直方图如何近似分布,以及小样本、极端值或箱边界造成的一项误判。
  • 问题 3|换一个情境,怎样继续应用?:对中性订单金额教学示例绘制 Q-Q 图,写出排序样本、理论分位数与单调性检查;不把视觉偏离写成真实订单分布的检验结论。
  • 作答提示:请完成三道题,并在回答中引用实际运行结果;拓展练习中不要把课堂示例写成普遍规律或因果结论。

教师参考解答|分箱敏感性

教师参考解答|答案与说明 1

  • 所用数据与字段:12个有限课堂收益率,无真实资产标签;单位为小数收益率。

教师参考解答|代码 1

展开代码(代码区可独立滚动)
import numpy as np, matplotlib.pyplot as plt  # 导入数组工具以完成矩阵运算与数值检查
classroom_returns=np.array([-.06,-.03,-.02,-.01,0,.005,.01,.012,.02,.025,.04,.08])  # 建立固定收益示例数据以比较分箱
return_q05=np.quantile(classroom_returns,.05); loss_var95=np.quantile(-classroom_returns,.95)  # 计算同一一期样本的收益左尾与正损失VaR
assert np.isclose(loss_var95,-return_q05)  # 核对两种符号口径对应同一经济量
fig,axes=plt.subplots(1,2,figsize=(8,3.2))  # 设置图形编码、坐标或布局以匹配数据口径
checks=[]  # 记录每个分箱或条件的核对依据
for ax,bins in zip(axes,[4,8]):  # 逐项遍历规定对象并保留每轮计算依据
    counts,edges,_=ax.hist(classroom_returns,bins=bins,edgecolor='white'); checks.append((bins,int(counts.sum()),edges))  # 建立固定收益示例数据以比较分箱
    ax.set_title(f'{bins}箱'); ax.set_xlabel('收益率'); ax.set_ylabel('频数')  # 设置图形编码、坐标或布局以匹配数据口径
plt.tight_layout(); plt.show()  # 设置图形编码、坐标或布局以匹配数据口径
assert all(total==len(classroom_returns) for _,total,_ in checks)  # 建立固定收益示例数据以比较分箱
print({'histogram_checks':checks,'return_q05':return_q05,'loss_var95':loss_var95,'confidence':'95%','holding_period':'1 period','estimator':'empirical quantile'})  # 同时报出分箱和VaR口径依据

教师参考解答|答案与说明 2

  • 拓展应用答案|订单金额 Q-Q 教学示例(非业务观测)

教师参考解答|代码 2

展开代码(代码区可独立滚动)
from scipy import stats  # 导入Q-Q理论分位数计算工具
order_amount_fixture=np.array([80,92,95,101,104,108,115,130,165,240],dtype=float)  # 构造不对应真实订单的确定性金额示例数据
(theoretical_quantiles,ordered_amounts),(qq_slope,qq_intercept,qq_r)=stats.probplot(order_amount_fixture,dist='norm')  # 计算正态理论分位数与排序样本点
plt.figure(figsize=(6.8,3.2))  # 创建投影可读的Q-Q画布
plt.scatter(theoretical_quantiles,ordered_amounts,label='教学示例点')  # 绘制中性教学示例的Q-Q散点
plt.plot(theoretical_quantiles,qq_intercept+qq_slope*theoretical_quantiles,label='参考直线')  # 绘制拟合参考线辅助识别尾部偏离
plt.title('教学示例,非业务观测|订单金额 Q-Q 图'); plt.xlabel('正态理论分位数'); plt.ylabel('示例数据金额'); plt.legend(); plt.tight_layout(); plt.show()  # 将依据边界写入可见标题与图例
assert np.allclose(ordered_amounts,np.sort(order_amount_fixture))  # 核对Q-Q纵坐标等于原样本排序值
print({'n':len(order_amount_fixture),'qq_r':qq_r,'max_amount':ordered_amounts[-1]})  # 输出样本量、拟合相关与尾部值

教师参考解答|答案与说明 3

  • 解释答案:直方图的柱高由分箱边界决定,改变箱数可能改变形状但不能改变样本总数;尾部、缺失和单位必须和图一起报告。收益左尾 \(Q_{0.05}(r)\) 通常为负,定义损失 \(L=-r\) 后,同一期经验损失 \(VaR_{95}(L)=-Q_{0.05}(r)\) 为正;报告必须写明符号、95% 置信水平、一期持有期和经验分位数估计法。
  • 拓展应用答案:上方代码把对象替换为确定性订单金额示例数据,保持“排序样本→理论分位数→参考线”步骤;输出Q-Q点、拟合相关和尾部值,并检查纵坐标等于原样本排序。图形只演示方法,不代表真实订单分布。
  • 参考结果:4箱/8箱两图且计数总和均为12;同一示例数据的收益 Q05、正损失 VaR95 及二者符号核对;以及标明“教学示例,非业务观测”的订单金额Q-Q图。另行核对:比较尾部观测在直方图与Q-Q图中是否都偏离主体,并核对分位数符号与排序检查。
  • 常见错误:随机序列贴真实股票名;把图形近似当正态检验;忽略箱边界敏感性。